昨天看過了 route()
的實作內
val selector = HttpMethodRouteSelector(method)
裡面的邏輯。
今天,我們來看看
return createRouteFromPath(path).createChild(selector).apply(build)
這段程式,是怎麼處理我們輸入的路徑的。
首先看 createRouteFromPath()
的實作
public fun RoutingBuilder.createRouteFromPath(path: String): RoutingBuilder {
val parts = RoutingPath.parse(path).parts
var current: RoutingBuilder = this
for (index in parts.indices) {
val (value, kind) = parts[index]
val selector = when (kind) {
RoutingPathSegmentKind.Parameter -> PathSegmentSelectorBuilder.parseParameter(value)
RoutingPathSegmentKind.Constant -> PathSegmentSelectorBuilder.parseConstant(value)
}
// there may already be entry with same selector, so join them
current = current.createChild(selector)
}
if (path.endsWith("/")) {
current = current.createChild(TrailingSlashRouteSelector)
}
return current
}
我們開始看 val parts = RoutingPath.parse(path).parts
RoutingPath
定義如下
public class RoutingPath private constructor(public val parts: List<RoutingPathSegment>)
RoutingPath.parse()
的實作則如下
public fun parse(path: String): RoutingPath {
if (path == "/") return root
val segments = path.splitToSequence("/").filter { it.isNotEmpty() }.map { segment ->
when {
segment.contains('{') && segment.contains('}') -> RoutingPathSegment(
segment,
RoutingPathSegmentKind.Parameter
)
else -> RoutingPathSegment(segment.decodeURLPart(), RoutingPathSegmentKind.Constant)
}
}
return RoutingPath(segments.toList())
}
我們一段一段的看這段程式碼
if (path == "/") return root
這段很單純:如果傳入的 path
為 /
,不需要做任何特殊處理,回傳
public val root: RoutingPath = RoutingPath(listOf())
即可。
如果是比較複雜的路徑,那就走到下一段處理:
val segments = path.splitToSequence("/").filter { it.isNotEmpty() }.map {
}
根據斜線拆分之後,過濾掉空的元素,然後利用 map()
做區分
when {
segment.contains('{') && segment.contains('}') -> RoutingPathSegment(
segment,
RoutingPathSegmentKind.Parameter
)
else -> RoutingPathSegment(segment.decodeURLPart(), RoutingPathSegmentKind.Constant)
}
根據命名,看起來是將路徑區分成 Parameter
和 Constant
。
要確認這個假設,我們進去看 RoutingPathSegment
和 RoutingPathSegmentKind
的實作
/**
* A single routing path segment.
* @property value - segment text value
* @property kind - segment kind (constant or parameter)
*/
public data class RoutingPathSegment(val value: String, val kind: RoutingPathSegmentKind)
/**
* Possible routing path segment kinds.
*/
public enum class RoutingPathSegmentKind {
/**
* A constant path segment.
*/
Constant,
/**
* A parameter path segment (a wildcard, a named parameter, or both).
*/
Parameter
}
僅僅用了一個 data class
和一個 enum class
,就成功地定義了路徑所需要的元素種類!
最後回傳 RoutingPath(segments.toList())
,所以前面的
val parts = RoutingPath.parse(path).parts
就可以拿到路徑拆分之後的 List<RoutingPathSegment>
了。
取得之後,就是針對這個 List
內每個元素進行操作
for (index in parts.indices) {
val (value, kind) = parts[index]
val selector = when (kind) {
RoutingPathSegmentKind.Parameter -> PathSegmentSelectorBuilder.parseParameter(value)
RoutingPathSegmentKind.Constant -> PathSegmentSelectorBuilder.parseConstant(value)
}
// there may already be entry with same selector, so join them
current = current.createChild(selector)
}
上面這段內,
val (value, kind) = parts[index]
利用了 Kotlin 程式語言的 Destructuring declarations,來簡化取得 RoutingPathSegment
的元素程式碼。
之後根據對應的 RoutingPathSegmentKind
選擇 parse 方式,
PathSegmentSelectorBuilder.parseParameter
實作如下
public fun parseParameter(value: String): RouteSelector {
val prefixIndex = value.indexOf('{')
val suffixIndex = value.lastIndexOf('}')
val prefix = if (prefixIndex == 0) null else value.substring(0, prefixIndex)
val suffix = if (suffixIndex == value.length - 1) null else value.substring(suffixIndex + 1)
val signature = value.substring(prefixIndex + 1, suffixIndex)
return when {
signature.endsWith("?") -> PathSegmentOptionalParameterRouteSelector(signature.dropLast(1), prefix, suffix)
signature.endsWith("...") -> {
if (suffix != null && suffix.isNotEmpty()) {
throw IllegalArgumentException("Suffix after tailcard is not supported")
}
PathSegmentTailcardRouteSelector(signature.dropLast(3), prefix ?: "")
}
else -> PathSegmentParameterRouteSelector(signature, prefix, suffix)
}
}
這段邏輯是很純粹的字串處理,狀態有一點多,但是邏輯相對單純。
PathSegmentSelectorBuilder.parseConstant
實作起來就要考慮一些狀況
public fun parseConstant(value: String): RouteSelector = when (value) {
"*" -> PathSegmentWildcardRouteSelector
else -> PathSegmentConstantRouteSelector(value)
}
根據輸入的元素,必須拆分成 PathSegmentWildcardRouteSelector
和 PathSegmentConstantRouteSelector
public object PathSegmentWildcardRouteSelector : RouteSelector() {
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
if (segmentIndex < context.segments.size && context.segments[segmentIndex].isNotEmpty()) {
return RouteSelectorEvaluation.WildcardPath
}
return RouteSelectorEvaluation.FailedPath
}
override fun toString(): String = "*"
}
/**
* Evaluates a route against a constant path segment.
* @param value is a value of the path segment
*/
public data class PathSegmentConstantRouteSelector(
val value: String
) : RouteSelector() {
@Suppress("UNUSED_PARAMETER")
@Deprecated(
"hasTrailingSlash is not used anymore. This is going to be removed",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("PathSegmentConstantRouteSelector(value)")
)
public constructor(value: String, hasTrailingSlash: Boolean) : this(value)
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation = when {
segmentIndex < context.segments.size && context.segments[segmentIndex] == value ->
RouteSelectorEvaluation.ConstantPath
else -> RouteSelectorEvaluation.FailedPath
}
override fun toString(): String = value
}
到這邊,我們終於追完了 createRouteFromPath(path)
的邏輯,
知道最終怎麼將一段路徑拆分成 RoutingBuilder
物件。
接著就是對這個物件做 createChild()
/**
* Creates a child node in this node with a given [selector] or returns an existing one with the same selector.
*/
public fun createChild(selector: RouteSelector): Route {
val existingEntry = childList.firstOrNull { it.selector == selector }
if (existingEntry == null) {
val entry = Route(this, selector, developmentMode, environment)
childList.add(entry)
return entry
}
return existingEntry
}
加入了新的 selector
之後,接著就是利用 apply(build)
對回傳的 Route
物件加上邏輯了。
到這邊,我們總算將程式碼內
routing {
get("/") {
}
}
這兩個函數的實作看過了一遍。沒想到要宣告一個路徑,框架背後竟然做了這麼多的事情呢!
明天我們就來看看回傳的設定 call.respondText("Hello World!")
這一段程式內,框架又幫我們做了多少的事情吧!